//=============================================================================
//
// Controls:
// - A, D, W, S: Move Camera
// - X, Z: Zoom in, zoom out 
//
//=============================================================================

#include "MainApplication.h"

namespace D3D10
{
	MainApplication::MainApplication(const HINSTANCE hInstance)
		: D3DFramework(hInstance)
		, mEffect(0)
		, mTechnique(0)
		, mInputLayout(0)
		, mEffectViewProjectionVariable(0)
		, mEffectWorldVariable(0)
		, mEffectElapsedTimeVariable(0)
		, mEffectEyePosVariable(0)
		, mEffectLightVariable(0)
		, mEyePos(0.0f, 0.0f, 0.0f)
		, mRadius(100.0f)
		, mTheta(0.0f)
		, mPhi(D3D10Utils::PI * 0.4f)
		, mTotalElapsedFrameTimes(1.0f)
	{
		D3DXMatrixIdentity(&mIcosahedronWorld);
		D3DXMatrixIdentity(&mView);
		D3DXMatrixIdentity(&mProjection);
		D3DXMatrixIdentity(&mViewProjection); 

		mClearColor = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);		
	}
	
	MainApplication::~MainApplication()
	{
		if (mDevice)
			mDevice->ClearState();
	
		D3D10Utils::ReleaseCOM(mEffect);
		D3D10Utils::ReleaseCOM(mInputLayout);
	}
	
	void MainApplication::init()
	{
		D3DFramework::init();

		initLights();		
		
		initEffect();
		initInputLayout();

		mSphere.init(mDevice, 10.0f, 30, 30);
	}
	
	void MainApplication::onResize()
	{
		D3DFramework::onResize();
	
		// Update projection perspective matrix.
		const float aspect = static_cast<float> (mClientWidth) / static_cast<float> (mClientHeight);
		D3DXMatrixPerspectiveFovLH(&mProjection, D3D10Utils::PI * 0.25f, aspect, 1.0f, 1000.0f);
	}
	
	void MainApplication::updateScene(const float deltaTime)
	{
		D3DFramework::updateScene(deltaTime);
	
		updateViewMatrix(deltaTime);
	}
	
	void MainApplication::drawScene()
	{
		D3DFramework::drawScene();
	
		// Restore default state
		mDevice->OMSetDepthStencilState(0, 0);
		
		// Restore blend state
		const float blendFactors[] = {0.0f, 0.0f, 0.0f, 0.0f};
		mDevice->OMSetBlendState(0, blendFactors, 0xffffffff);

		mDevice->IASetInputLayout(mInputLayout);
		mDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

		// Set per frame constants.
		mEffectEyePosVariable->SetRawValue(&mEyePos, 0, sizeof(D3DXVECTOR3));
		mEffectLightVariable->SetRawValue(&mParallelLight, 0, sizeof(D3D10Utils::Light));
		mTotalElapsedFrameTimes += 0.01f;
		mEffectElapsedTimeVariable->SetFloat(mTotalElapsedFrameTimes);

		D3D10_TECHNIQUE_DESC techDesc;
		mTechnique->GetDesc(&techDesc);

		ID3D10EffectPass* pass = mTechnique->GetPassByIndex(0);

		// Draw land
		mViewProjection = mView * mProjection;
		mEffectViewProjectionVariable->SetMatrix(reinterpret_cast<float*> (&mViewProjection));
		mEffectWorldVariable->SetMatrix(reinterpret_cast<float*> (&mIcosahedronWorld));
		pass->Apply(0);
		mSphere.draw();

		// Restore default rasterizer state.
		RECT R = {5, 5, 0, 0};
		mDevice->RSSetState(0);
		mFont->DrawText(0, mFrameStats.c_str(), -1, &R, DT_NOCLIP, D3D10Utils::WHITE);
	
		mSwapChain->Present(0, 0);
	}

	void MainApplication::initEffect()
	{
		DWORD shaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
		shaderFlags |= D3D10_SHADER_DEBUG;
		shaderFlags |= D3D10_SHADER_SKIP_OPTIMIZATION;
#endif

		ID3D10Blob* compilationErrors = 0;
		HRESULT hr = 0;
		hr = D3DX10CreateEffectFromFile(L"icosahedron/texAlpha.fx", 0, 0, 
			"fx_4_0", shaderFlags, 0, mDevice, 0, 0, &mEffect, &compilationErrors, 0);
		if (FAILED(hr))
		{
			if (compilationErrors)
			{
				MessageBoxA(0, (char*)compilationErrors->GetBufferPointer(), 0, 0);
				D3D10Utils::ReleaseCOM(compilationErrors);
			}
			DXTrace(__FILE__, (DWORD)__LINE__, hr, L"D3DX10CreateEffectFromFile", true);
		} 

		// Get shader variables
		mTechnique = mEffect->GetTechniqueByName("TexAlphaTech");
		mEffectViewProjectionVariable = mEffect->GetVariableByName("gViewProjection")->AsMatrix();
		mEffectWorldVariable = mEffect->GetVariableByName("gWorld")->AsMatrix();
		mEffectElapsedTimeVariable = mEffect->GetVariableByName("gElapsedTime")->AsScalar();
		mEffectEyePosVariable = mEffect->GetVariableByName("gEyePosW");
		mEffectLightVariable = mEffect->GetVariableByName("gLight");
	}
	
	void MainApplication::initInputLayout()
	{
		D3D10_INPUT_ELEMENT_DESC vertexDesc[] =
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D10_INPUT_PER_VERTEX_DATA, 0},
			{"NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0},
			{"TEXCOORD",  0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D10_INPUT_PER_VERTEX_DATA, 0},
		};
	
		D3D10_PASS_DESC PassDesc;
		mTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
		HRESULT hr = mDevice->CreateInputLayout(vertexDesc, 3, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize, &mInputLayout);

		D3D10Utils::ErrorHandler(hr);
	}

	void MainApplication::initLights()
	{
		// Set parallel light
		mParallelLight.mDir = D3DXVECTOR3(0.57735f, -0.57735f, 0.57735f);
		mParallelLight.mAmbient = D3DXCOLOR(0.4f, 0.4f, 0.4f, 1.0f);
		mParallelLight.mDiffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
		mParallelLight.mSpecular = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);	
	}

	void MainApplication::updateViewMatrix(const float deltaTime)
	{
		// Update angles based on input to orbit camera around scene.
		if (GetAsyncKeyState('A') & 0x8000)	
			mTheta -= 1.0f * deltaTime;
		if (GetAsyncKeyState('D') & 0x8000)	
			mTheta += 1.0f * deltaTime;
		if (GetAsyncKeyState('W') & 0x8000)	
			mPhi -= 2.0f * deltaTime;
		if (GetAsyncKeyState('S') & 0x8000)	
			mPhi += 2.0f * deltaTime;
		if (GetAsyncKeyState('Z') & 0x8000)	
			mRadius -= 25.0f * deltaTime;
		if (GetAsyncKeyState('X') & 0x8000)	
			mRadius += 25.0f * deltaTime;

		// Restrict the angle mPhi and radius mRadius.
		if (mPhi < 0.1f)	
			mPhi = 0.1f;
		if (mPhi > D3D10Utils::PI - 0.1f)	
			mPhi = D3D10Utils::PI - 0.1f;

		if (mRadius < 25.0f) 
			mRadius = 25.0f;

		// Convert Spherical to Cartesian coordinates: mPhi measured from +y
		// and mTheta measured counterclockwise from -z.
		mEyePos.x =  mRadius * sinf(mPhi) * sinf(mTheta);
		mEyePos.z = -mRadius * sinf(mPhi) * cosf(mTheta);
		mEyePos.y =  mRadius * cosf(mPhi);

		// Build the view matrix.
		D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
		D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
		D3DXMatrixLookAtLH(&mView, &mEyePos, &target, &up);
	}		
}
